#region Using

using System;
using System.IO;
using System.Xml;
using System.Globalization;
using System.Collections.Generic;

#endregion

namespace SemanticEngine.Apml
{
	public class ApmlWriter : WriterBase, IDisposable
	{

		#region Constructors

		public ApmlWriter(ApmlDocument doc)
		{
			_Document = doc;
		}

		#endregion

		#region Properties

		private Stream _Stream;

		private ApmlDocument _Document;

		public ApmlDocument Document
		{
			get { return _Document; }
			set { _Document = value; }
		}

		#endregion

		#region Methods

		public void WriteToStream(Stream stream)
		{
			_Stream = stream;
			WriteXml();
		}

		private void WriteXml()
		{
			if (!Document.IsValid)
			{
				throw new NotSupportedException(Document.ValidationMessage);
			}

			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Indent = true;

			using (XmlWriter writer = XmlWriter.Create(_Stream, settings))
			{
				writer.WriteStartDocument();

				writer.WriteStartElement("APML", "http://www.apml.org/apml-0.6");
				writer.WriteAttributeString("version", "0.6");

				WriteHead(writer);
				WriteBody(writer);

				writer.WriteEndElement(); // APML
			}
		}

		private void WriteHead(XmlWriter writer)
		{
			writer.WriteStartElement("Head");

			writer.WriteElementString("Title", Document.Title);
			writer.WriteElementString("Generator", Document.Generator);
			writer.WriteElementString("UserEmail", Document.UserEmail);
			writer.WriteElementString("DateCreated", Document.DateCreated.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture));

			writer.WriteEndElement(); // Head
		}

		private void WriteBody(XmlWriter writer)
		{
			writer.WriteStartElement("Body");

			if (Document.DefaultProfile != null)
			{
				writer.WriteAttributeString("defaultprofile", Document.DefaultProfile.Name);
			}

			foreach (ApmlProfile profile in Document.Profiles)
			{
				WriteProfile(writer, profile);
			}

			writer.WriteEndElement(); // Body
		}

		private void WriteProfile(XmlWriter writer, ApmlProfile profile)
		{
			writer.WriteStartElement("Profile");
			writer.WriteAttributeString("name", profile.Name);

			List<Concept> explicitConcepts = profile.Concepts.FindAll(delegate(Concept c) { return c.DataType == DataType.Explicit; });
			List<Concept> ImplicitConcepts = profile.Concepts.FindAll(delegate(Concept c) { return c.DataType == DataType.Implicit; });
			List<Source> ExplicitSources = profile.Sources.FindAll(delegate(Source s) { return s.DataType == DataType.Explicit; });
			List<Source> ImplicitSources = profile.Sources.FindAll(delegate(Source s) { return s.DataType == DataType.Implicit; });

			if (explicitConcepts.Count > 0 || ExplicitSources.Count > 0)
			{
				writer.WriteStartElement("ExplicitData");

				if (explicitConcepts.Count > 0)
					WriteConcepts(writer, explicitConcepts);

				if (ExplicitSources.Count > 0)
					WriteSources(writer, ExplicitSources);

				writer.WriteEndElement(); // ExplicitData	
			}

			if (ImplicitConcepts.Count > 0 || ImplicitSources.Count > 0)
			{
				writer.WriteStartElement("ImplicitData");

				if (ImplicitConcepts.Count > 0)
					WriteConcepts(writer, ImplicitConcepts);

				if (ImplicitSources.Count > 0)
					WriteSources(writer, ImplicitSources);

				writer.WriteEndElement(); // ImplicitData
			}

			writer.WriteEndElement(); // Profile
		}

		private void WriteConcepts(XmlWriter writer, List<Concept> concepts)
		{
			if (concepts.Count > 0)
			{
				concepts.Sort(delegate(Concept c1, Concept c2)
				{
					return c2.Rank.CompareTo(c1.Rank);
				});

				writer.WriteStartElement("Concepts");

				foreach (Concept concept in concepts)
				{
					writer.WriteStartElement("Concept");

					WriteAttribute(writer, "key", concept.Name);
					WriteAttribute(writer, "value", concept.Rank);
					WriteAttribute(writer, "from", concept.From ?? Document.DefaultFrom);
					WriteAttribute(writer, "updated", concept.DateUpdated);

					writer.WriteEndElement(); // Concept
				}

				writer.WriteEndElement(); // Concepts
			}
		}

		private void WriteSources(XmlWriter writer, List<Source> sources)
		{
			if (sources.Count > 0)
			{
				sources.Sort(delegate(Source s1, Source s2)
				{
					return s2.Rank.CompareTo(s1.Rank);
				});

				writer.WriteStartElement("Sources");

				foreach (Source source in sources)
				{
					writer.WriteStartElement("Source");

					WriteAttribute(writer, "key", source.Url.ToString());
					WriteAttribute(writer, "value", source.Rank);
					WriteAttribute(writer, "name", source.Name);
					WriteAttribute(writer, "type", source.ContentType);
					WriteAttribute(writer, "from", source.From ?? Document.DefaultFrom);
					WriteAttribute(writer, "updated", source.DateUpdated);

					if (source.Author != null)
					{
						writer.WriteStartElement("Author");

						WriteAttribute(writer, "key", source.Author.Name);
						WriteAttribute(writer, "value", source.Author.Rank);
						WriteAttribute(writer, "from", source.Author.From ?? Document.DefaultFrom);
						WriteAttribute(writer, "updated", source.Author.DateUpdated);

						writer.WriteEndElement(); // Author
					}

					writer.WriteEndElement(); // Source
				}
				writer.WriteEndElement(); // Sources
			}
		}

		#endregion

		#region IDisposable Members

		private void Dispose(bool dispose)
		{
			if (dispose)
			{
				if (_Stream != null)
				{
					_Stream.Dispose();
				}

				_Document = null;
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{		
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion
	}
}
